// Main loop part of HaxServ
//
// Written by: Test_User <hax@andrewyu.org>
//
// This is free and unencumbered software released into the public
// domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

#include <gnutls/gnutls.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

#include "network.h"
#include "config.h"
#include "types.h"
#include "tls.h"
#include "types.h"

void *client_loop(void *ign) {
	pthread_mutex_lock(&send_lock);
	while (1) {
		struct string full_msg = {.data = malloc(0), .len = 0};
		pthread_mutex_unlock(&send_lock);
		client_fd = accept(client_listen_fd, NULL, NULL);
		pthread_mutex_lock(&send_lock);
		listen(client_listen_fd, 0);
		client_connected = 0;
		while (1) {
			char data[512];
			pthread_mutex_unlock(&send_lock); // TODO: proper locking, this works for now but is certainly inefficient
			uint64_t new_len;
			{
				ssize_t len = read(client_fd, data, 512);
				if (len < 0)
					new_len = 0;
				else
					new_len = (size_t)len;
			}
			pthread_mutex_lock(&send_lock);

			if (new_len == 0) {
				goto disconnect_client;
			}

			uint8_t found = 0;
			uint64_t msg_len;
			for (uint64_t i = 0; i < new_len; i++) {
				if (data[i] == '\n') {
					found = 1;
					msg_len = i + full_msg.len;
					break;
				}
			}

			void *tmp = realloc(full_msg.data, full_msg.len+new_len);
			if (tmp == 0 && full_msg.len+new_len != 0) {
				WRITES(2, STRING("OOM... disconnect client.\n"));
				goto disconnect_client;
			}
			full_msg.data = tmp;

			memcpy(full_msg.data+full_msg.len, data, new_len);

			full_msg.len += new_len;

			if (!found)
				continue;

			while (1) {
				if (full_msg.data[msg_len - 1] == '\r')
					msg_len--;

				uint64_t offset = 0;
				while (offset < msg_len && full_msg.data[offset] == ' ')
					offset++;

				if (offset == msg_len) {
					puts("Protocol violation: No command.");
					goto disconnect_client;
				}

				struct string command;
				command.data = full_msg.data+offset;
				found = 0;
				for (uint64_t i = offset; i < msg_len; i++) {
					if (full_msg.data[i] == ' ') {
						found = 1;
						command.len = i - offset;
						offset = i;
						break;
					}
				}
				if (!found) {
					command.len = msg_len - offset;
					offset = msg_len;
				}

				while (offset < msg_len && full_msg.data[offset] == ' ')
					offset++;

				uint64_t argc = 0;
				uint64_t old_offset = offset;
				if (offset < msg_len) {
					while (offset < msg_len) {
						if (full_msg.data[offset] == ':') {
							argc++;
							break;
						}

						while (offset < msg_len && full_msg.data[offset] != ' ')
							offset++;

						argc++;

						while (offset < msg_len && full_msg.data[offset] == ' ')
							offset++;
					}
				}
				offset = old_offset;

				struct string argv[argc];
				if (offset < msg_len) {
					uint64_t i = 0;
					while (offset < msg_len) {
						if (full_msg.data[offset] == ':') {
							argv[i].data = full_msg.data+offset+1;
							argv[i].len = msg_len - offset - 1;
							break;
						}

						argv[i].data = full_msg.data+offset;
						uint64_t start = offset;

						while (offset < msg_len && full_msg.data[offset] != ' ')
							offset++;

						argv[i].len = offset - start;

						while (offset < msg_len && full_msg.data[offset] == ' ')
							offset++;

						i++;
					}
				}

				int (*func)(uint64_t argc, struct string *argv) = get_table_index(client_network_commands, command);

#if LOGALL
#if COLORIZE
				WRITES(1, STRING("\x1b[34m[Client->Us] \x1b[33m"));
#else
				WRITES(1, STRING("[Client->Us] "));
#endif
#if COLORIZE
				write(1, full_msg.data, msg_len);
				WRITES(1, STRING("\x1b[0m\n"));
#else
				write(1, full_msg.data, msg_len+(full_msg.data[msg_len] == '\r' ? 2 : 1)); // +2 or 1: \r\n or \n
#endif
#endif

				if (func == 0) {
#if !LOGALL
					WRITES(2, STRING("[Client] "));
					write(2, full_msg.data, msg_len+(full_msg.data[msg_len] == '\r' ? 2 : 1));
#endif
					WRITES(2, STRING("WARNING: Command is unknown, ignoring...\n"));
					WRITES(2, STRING("\n"));
				} else {
#if LOGALL
					WRITES(1, STRING("\n"));
#endif
					int err = func(argc, argv);
					if (err) {
						WRITES(1, STRING("Disconnecting client by result of the network command handler...\n"));
						goto disconnect_client;
					}
				}

				if (full_msg.data[msg_len] == '\r')
					msg_len++;
				memmove(full_msg.data, full_msg.data+msg_len+1, full_msg.len - msg_len - 1);
				full_msg.len -= msg_len+1;

				found = 0;
				for (uint64_t i = 0; i < full_msg.len; i++) {
					if (full_msg.data[i] == '\n') {
						found = 1;
						msg_len = i;
						break;
					}
				}

				if (found == 0) {
					void *tmp = realloc(full_msg.data, full_msg.len);
					if (tmp == 0 && full_msg.len != 0) {
						puts("AAAAAAAAA (OOM shrinking allocated data?)");
						goto disconnect_client;
					}
					full_msg.data = tmp;

					break;
				}
			}
		}
		disconnect_client:
		if (client_connected) {
			SEND(STRING(":1HC000001 QUIT :Ping timeout: -240 seconds\n"));
			client_connected = 0;
			remove_user(STRING("1HC000001"), STRING("Ping timeout: -240 seconds\n"));
		}

		close(client_fd);
		client_fd = -1;
		free(full_msg.data);
		listen(client_listen_fd, 1);
	}
}

pthread_t client_thread_id;
int main(void) {
	if (initservernetwork() != 0)
		return 1;
	if (initclientnetwork() != 0)
		return 1;

	pthread_create(&client_thread_id, NULL, client_loop, NULL);

	pthread_mutex_lock(&send_lock);
	struct string full_msg = {malloc(0), 0};
	while (1) {
		char data[512];

		char timeout;
		uint64_t new_len;
		char last_timeout = 0;
		while (1) {
			pthread_mutex_unlock(&send_lock);
			new_len = RECV(data, sizeof(data), &timeout);
			pthread_mutex_lock(&send_lock);
			if (!timeout)
				break;

			if (last_timeout) {
				break;
			} else {
				SEND(STRING(":1HC PING 1HC 100\n")); // TODO: Fix this as well
				last_timeout = 1;
			}
		}

		if (new_len == 0) {
			WRITES(1, STRING("Disconnected.\n"));
			return 0;
		}

		uint8_t found = 0;
		uint64_t msg_len;
		for (uint64_t i = 0; i < new_len; i++) {
			if (data[i] == '\n') {
				found = 1;
				msg_len = i + full_msg.len;
				break;
			}
		}

		void *tmp = realloc(full_msg.data, full_msg.len+new_len);
		if (tmp == 0 && full_msg.len+new_len != 0) {
			WRITES(2, STRING("OOM... currently just exiting bc there's no automatic reconnect in here yet, and the only sane solution to this is resyncing.\n"));
			return 1;
		}
		full_msg.data = tmp;

		memcpy(full_msg.data+full_msg.len, data, new_len);

		full_msg.len += new_len;

		if (!found)
			continue;

		while (1) {
			uint64_t offset = 0;
			while (offset < msg_len && full_msg.data[offset] == ' ')
				offset++;

			if (msg_len == offset) {
				WRITES(2, STRING("Protocol violation: Empty message.\n"));
				return 2;
			}

			struct string source;
			if (full_msg.data[offset] == ':') {
				source.data = full_msg.data + 1 + offset;
				found = 0;
				for (uint64_t i = offset + 1; i < msg_len; i++) {
					if (full_msg.data[i] == ' ') {
						found = 1;
						source.len = i - offset - 1;
						offset = i + 1;
						break;
					}
				}
				if (!found || source.len + 1 == msg_len) {
					WRITES(2, STRING("Protocol violation: Sender but no command."));
					return 2;
				}
			} else {
				source = (struct string){0};
			}

			while (offset < msg_len && full_msg.data[offset] == ' ')
				offset++;

			if (offset == msg_len) {
				WRITES(2, STRING("Protocol violation: No command."));
				return 2;
			}

			struct string command;
			command.data = full_msg.data+offset;
			found = 0;
			for (uint64_t i = offset; i < msg_len; i++) {
				if (full_msg.data[i] == ' ') {
					found = 1;
					command.len = i - offset;
					offset = i;
					break;
				}
			}
			if (!found) {
				command.len = msg_len - offset;
				offset = msg_len;
			}

			while (offset < msg_len && full_msg.data[offset] == ' ')
				offset++;

			uint64_t argc = 0;
			uint64_t old_offset = offset;
			if (offset < msg_len) {
				while (offset < msg_len) {
					if (full_msg.data[offset] == ':') {
						argc++;
						break;
					}

					while (offset < msg_len && full_msg.data[offset] != ' ')
						offset++;

					argc++;

					while (offset < msg_len && full_msg.data[offset] == ' ')
						offset++;
				}
			}
			offset = old_offset;

			struct string argv[argc];
			if (offset < msg_len) {
				uint64_t i = 0;
				while (offset < msg_len) {
					if (full_msg.data[offset] == ':') {
						argv[i].data = full_msg.data+offset+1;
						argv[i].len = msg_len - offset - 1;
						break;
					}

					argv[i].data = full_msg.data+offset;
					uint64_t start = offset;

					while (offset < msg_len && full_msg.data[offset] != ' ')
						offset++;

					argv[i].len = offset - start;

					while (offset < msg_len && full_msg.data[offset] == ' ')
						offset++;

					i++;
				}
			}

			int (*func)(struct string source, uint64_t argc, struct string *argv) = get_table_index(server_network_commands, command);

#if LOGALL
#if COLORIZE
			WRITES(1, STRING("\x1b[35m[Server->Us] \x1b[36m"));
#else
			WRITES(1, STRING("[Server->Us] "));
#endif
#if COLORIZE
			write(1, full_msg.data, msg_len);
			WRITES(1, STRING("\x1b[0m\n"));
#else
			write(1, full_msg.data, msg_len+1); // +1: \n
#endif
#endif

			if (func == 0) {
#if !LOGALL
				WRITES(2, STRING("[Server] "));
				write(2, full_msg.data, msg_len+1); // +1: \n
#endif
				WRITES(2, STRING("WARNING: Command is unknown, ignoring...\n"));
				WRITES(2, STRING("\n"));
			} else {
#if LOGALL
				WRITES(1, STRING("\n"));
#endif
				int err = func(source, argc, argv);
				if (err) {
#if !LOGALL
					WRITES(2, STRING("Message was: [Server] "));
					write(2, full_msg.data, msg_len+1);
#endif
					WRITES(1, STRING("Disconnecting by result of the network command handler...\n"));
					return 0;
				}
			}

			memmove(full_msg.data, full_msg.data+msg_len+1, full_msg.len - msg_len - 1);
			full_msg.len -= msg_len+1;

			found = 0;
			for (uint64_t i = 0; i < full_msg.len; i++) {
				if (full_msg.data[i] == '\n') {
					found = 1;
					msg_len = i;
					break;
				}
			}

			if (found == 0) {
				void *tmp = realloc(full_msg.data, full_msg.len);
				if (tmp == 0 && full_msg.len != 0) {
					puts("AAAAAAAAA (OOM shrinking allocated data?)");
					return 1;
				}
				full_msg.data = tmp;

				break;
			}
		}
	}

	return 0;
}
